package com.ttxs.utils.xml;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.sun.org.apache.xml.internal.serialize.OutputFormat;
import com.sun.org.apache.xml.internal.serialize.XMLSerializer;
import com.ttxs.utils.FileUtils;
import org.w3c.dom.*;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;


/**
 * Xml工具类   格式转换、格式验证、格式化功能
 *
 * @author cn
 */
public class XmlUtils {

    private static void begin(String url){
        if (!xmlValidator(url)){
            System.out.println("XML文件格式错误");
        }
    }

    public static String convertFromXml(String inputPath) {
        String str = FileUtils.readFileToString(inputPath, "UTF-8");
        boolean flag = true;
        boolean quotesFlag = true;
        StringBuffer ans = new StringBuffer();
        String tmp = "";
        for (int i = 0; i < str.length(); i++) {
            if ('"' == str.charAt(i)) {
                ans.append(str.charAt(i));
                quotesFlag = !quotesFlag;
            } else if ('<' == str.charAt(i)) {
                tmp = tmp.trim();
                ans.append(tmp);
                flag = true;
                ans.append(str.charAt(i));
            } else if ('>' == str.charAt(i)) {
                if(quotesFlag){
                    flag = false;
                    ans.append(str.charAt(i));
                    tmp = "";
                }else{
                    ans.append("&gt;");
                }
            } else if (flag) {
                ans.append(str.charAt(i));
            } else {
                tmp += str.charAt(i);
            }
        }
        return ans.toString();
    }


    /**
     * 压缩xml文件：删除多余的空白行,空格,制表符
     *
     * @param url xml文件地址
     * @return 压缩后的结果字符串
     */
    public static String xmlCompression(String url) {

        try {
            // 创建 DocumentBuilderFactory
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

            //设置解析器的选项，包括忽略注释、合并相邻文本节点以及启用命名空间支持。
            factory.setIgnoringComments(true);
            factory.setCoalescing(true);
            factory.setNamespaceAware(true);

            // 创建 DocumentBuilder
            DocumentBuilder builder = factory.newDocumentBuilder();

            // 解析输入文件，获取 Document 对象
            FileInputStream fis = new FileInputStream(url);
            Document document = builder.parse(new InputSource(fis));

            // 使用 Xerces 库的 XMLSerializer 类进行压缩
            OutputFormat format = new OutputFormat(document);
            format.setIndenting(false); // 禁用缩进
            format.setOmitXMLDeclaration(true); // 省略 XML 声明
            format.setPreserveSpace(false); // 禁用空格保留

            // 创建字符串输出流
            StringWriter writer = new StringWriter();

            // 创建 XMLSerializer 对象并执行压缩
            XMLSerializer serializer = new XMLSerializer(writer, format);
            serializer.serialize(document);

            // 删除多余的空白行
            String compressedXML = writer.toString();
            compressedXML = compressedXML.replaceAll("(?m)^[ \t]*\r?\n", "");

            return compressedXML;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }


    /**
     * Xml格式化
     *
     * @param url Xml文件路径
     * @return 格式化后结果
     */
    public static String xmlFormatter(String url) {
        String formattedXml="";
        try {
            //去除压缩xml,去除多余的空白行,空格,制表符
            String compressionXml = xmlCompression(url);
            //对压缩后字符串，进行格式化，增加缩进
            formattedXml=formatXml(compressionXml);
            return formattedXml;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    /**
     * 格式化算法,给出缩进层次
     *
     * @param xmlString
     * @return 结果字符串
     */
    private static String formatXml(String xmlString) {
        try {
            Document document = DocumentBuilderFactory.newInstance()
                    .newDocumentBuilder()
                    .parse(new InputSource(new StringReader(xmlString)));

            TransformerFactory transformerFactory = TransformerFactory.newInstance();
            Transformer transformer = transformerFactory.newTransformer();
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); // 设置缩进量为4个空格

            StringWriter writer = new StringWriter();
            transformer.transform(new DOMSource(document), new StreamResult(writer));

            return writer.toString();
        } catch (Exception e) {
            e.printStackTrace();
            return xmlString; // 如果出现异常，返回原始的未格式化的 XML
        }

    }


    /**
     * 验证xml格式
     *
     * @param url
     * @return 验证结果
     */
    public static Boolean xmlValidator(String url) {
        return validateXml(url);
    }

    /**
     * 验证xml算法
     *
     * @param xmlFilePath
     * @return 验证正确性:true ,false
     */
    private static boolean validateXml(String xmlFilePath) {
        /*
        添加了一个额外的 catch 块来捕获并处理其他类型的异常。这样，无论是什么类型的异常，
        都不会打印异常的堆栈信息到控制台。
         */
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setValidating(true);
            DocumentBuilder builder = factory.newDocumentBuilder();
            builder.setErrorHandler(null); // 禁用错误处理程序以防止打印验证错误
            builder.parse(new File(xmlFilePath));
            return true;
        } catch (ParserConfigurationException | IOException e) {
            e.printStackTrace();
            return false;
        } catch (Exception e) {
            return false;
        }
    }


    /**
     * xml转换成json
     *
     * @param url 进行转换xml的文件地址
     * @return json字符串
     */
    public static String xml2Json(String url) {
        begin(url);
        try {


            String xmlData = new String(Files.readAllBytes(Paths.get(url)), StandardCharsets.UTF_8);

            // 创建一个DocumentBuilder对象，用于解析XML数据

            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();

            // 解析XML数据并获取Document对象
            Document document = builder.parse(new InputSource(new StringReader(xmlData)));

            // 创建根节点的JSON对象
            JSONObject json = new JSONObject();
            Element rootElement = document.getDocumentElement();

            json.put(rootElement.getNodeName(), analyzeXml(rootElement));


            String jsonString = json.toString();
            JSONObject jsonObject = JSON.parseObject(jsonString);
            Gson gson = new GsonBuilder().setPrettyPrinting().create();

            // 格式化输出
            String formattedJsonString = gson.toJson(jsonObject);

            return formattedJsonString;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    /**
     * xml使用递归，转换成json实现算法
     *
     * @param element XML文档的根元素
     * @return 返回json
     */

    private static Object analyzeXml(Element element) {
        JSONObject json = new JSONObject();

        // 处理子节点
        NodeList childNodes = element.getChildNodes();
        for (int i = 0; i < childNodes.getLength(); i++) {
            Node childNode = childNodes.item(i);

            if (childNode.getNodeType() == Node.ELEMENT_NODE) {
                // 如果子节点是元素节点，则递归处理
                Element childElement = (Element) childNode;
                String childTagName = childElement.getTagName();
                Object childValue = analyzeXml(childElement);

                // 判断是否已经存在同名的子节点，若存在则转换为JSON数组
                if (json.containsKey(childTagName)) {
                    Object existingValue = json.get(childTagName);
                    if (existingValue instanceof JSONArray) {
                        // 如果已经是数组，则直接添加到数组中
                        ((JSONArray) existingValue).add(childValue);
                    } else {
                        // 如果还不是数组，则创建新的数组并添加元素
                        JSONArray jsonArray = new JSONArray();
                        jsonArray.add(existingValue);
                        jsonArray.add(childValue);
                        json.put(childTagName, jsonArray);
                    }
                } else {
                    json.put(childTagName, childValue);
                }
            }
        }
        // 处理属性
        NamedNodeMap attributes = element.getAttributes();
        for (int i = 0; i < attributes.getLength(); i++) {
            Node attribute = attributes.item(i);
            String attributeName = attribute.getNodeName();
            String attributeValue = attribute.getNodeValue();
            json.put(attributeName, attributeValue);
        }

        // 如果没有子节点和属性，直接返回文本内容作为节点值
        if (json.isEmpty()) {
            return element.getTextContent();
        } else {
            return json;
        }
    }


    /**
     * xml转Csv
     *
     * @param url
     * @return Csv字符串
     */
    public static String xml2Csv(String url) {
        begin(url);
        try {
            File file_xml = new File(url);
            char c = ' ';
            StringBuilder xmlString = new StringBuilder();
            InputStream is_xml = new FileInputStream(file_xml);
            for (int i = 0; i < file_xml.length(); i++) {
                c = (char) is_xml.read();
                xmlString.append(c);
            }

            int uniqueTagCount = countUniqueInnermostTags(xmlString.toString());
            String innermostTags = "";
            String innermostTexts = "";

            try {
                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                DocumentBuilder builder = factory.newDocumentBuilder();
                Document document = builder.parse(new InputSource(new StringReader(xmlString.toString())));

                NodeList nodeList = document.getElementsByTagName("*");
                for (int i = 0; i < nodeList.getLength(); i++) {
                    Node node = nodeList.item(i);
                    //找到最里面的标签，
                    if (node.getNodeType() == Node.ELEMENT_NODE && !hasChildElements(node)) {
                        String tag = node.getNodeName();
//                    getTextContent() 方法获取节点的文本内容，
                        // 然后使用 trim() 方法去除字符串两端的空白字符。
                        String text = node.getTextContent().trim();
                        innermostTags += tag + ",";
                        innermostTexts += text + ",";
                    }
                }
            } catch (ParserConfigurationException | SAXException e) {
                throw new RuntimeException(e);
            }
            //标签和文本list组
            List<String> tagGroups = splitStringAtCommas(innermostTags, uniqueTagCount);
            List<String> textGroups = splitStringAtCommas(innermostTexts, uniqueTagCount);


            String s = tagGroups.get(0);
            if (tagGroups.get(0).endsWith(",")) {
                s = tagGroups.get(0).substring(0, tagGroups.get(0).length() - 1);
            }
            String s2 = "";
            String t = "";
            for (String group : textGroups) {
                t = group.substring(0, group.length() - 1);
//            System.out.println(s);
                s2 = t + "\n" + s2;
            }
            return s + "\n" + s2;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }


    /**
     * 搜索最里面不重名标签的数量
     *
     * @param xmlString
     * @return 不重名标签的数量
     */
    public static int countUniqueInnermostTags(String xmlString) {
        Map<String, Integer> tagCountMap = new HashMap<>();

        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document document = builder.parse(new InputSource(new StringReader(xmlString)));

            NodeList nodeList = document.getElementsByTagName("*");
            for (int i = 0; i < nodeList.getLength(); i++) {
                Node node = nodeList.item(i);
                if (node.getNodeType() == Node.ELEMENT_NODE && !hasChildElements(node)) {
                    String tag = node.getNodeName();
                    tagCountMap.put(tag, tagCountMap.getOrDefault(tag, 0) + 1);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return tagCountMap.size();
    }


    /**
     * hasChildElements 方法用于检查给定的 Node 是否包含子元素节点
     *
     * @param node
     * @return true, false
     */

    private static boolean hasChildElements(Node node) {
        NodeList childNodes = node.getChildNodes();
        for (int i = 0; i < childNodes.getLength(); i++) {
            Node childNode = childNodes.item(i);
            if (childNode.getNodeType() == Node.ELEMENT_NODE) {
                return true;
            }
        }
        return false;
    }

    /**
     * splitStringAtCommas 方法用于将字符串按逗号进行分割，并按指定的分组大小进行分组。
     *
     * @param str，字符串
     * @param n，分组的数目
     * @return 分组结果
     */
    private static List<String> splitStringAtCommas(String str, int n) {
        List<String> result = new ArrayList<>();
        String[] parts = str.split(",");
        StringBuilder group = new StringBuilder();
        for (int i = 0; i < parts.length; i++) {
            group.append(parts[i]).append(",");
            if ((i + 1) % n == 0 || i == parts.length - 1) {
                result.add(group.toString());
                group = new StringBuilder();
            }
        }
        return result;
    }


    /**
     * xml转换成Yaml
     *
     * @param url
     * @return yaml字符串
     */

    public static String xml2Yaml(String url) {
        begin(url);
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document document = builder.parse(new File(url));

            // 创建YAML生成器
            DumperOptions options = new DumperOptions();
            options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
            Yaml yaml = new Yaml(options);

            // 将Document对象转换为YAML格式字符串
            return convertDocumentToYaml(document.getDocumentElement(), yaml);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }


    /**
     * 将整个 XML 文档转换为 YAML 字符串。
     *
     * @param element，xml元素
     * @param yaml，对象
     * @return YAML 字符串。
     */
    private static String convertDocumentToYaml(Element element, Yaml yaml) {
        Map<String, Object> yamlMap = new LinkedHashMap<>();
        convertElementToYaml(element, yamlMap);

        Map<String, Object> rootMap = new LinkedHashMap<>();
        rootMap.put(element.getNodeName(), yamlMap);

        return yaml.dump(rootMap);
    }

    /**
     * 递归方法，将给定的 XML 元素及其子元素转换为 YAML 格式，并存储在 yamlMap 中。
     *
     * @param element，xml元素
     * @param yamlMap，yaml集合
     */
    private static void convertElementToYaml(Element element, Map<String, Object> yamlMap) {
        NodeList childNodes = element.getChildNodes();

        // 遍历子节点
        for (int i = 0; i < childNodes.getLength(); i++) {
            Node childNode = childNodes.item(i);
            if (childNode instanceof Element) {
                Element childElement = (Element) childNode;
                String nodeName = childElement.getNodeName();
                Object existingValue = yamlMap.get(nodeName);
                if (existingValue != null) {
                    if (existingValue instanceof List) {
                        ((List<Object>) existingValue).add(getNodeValue(childElement));
                    } else {
                        List<Object> valueList = new ArrayList<>();
                        valueList.add(existingValue);
                        valueList.add(getNodeValue(childElement));
                        yamlMap.put(nodeName, valueList);
                    }
                } else {
                    yamlMap.put(nodeName, getNodeValue(childElement));
                }
            }
        }
    }

    /**
     * 辅助方法，用于获取给定元素的值。
     *
     * @param element，xml元素
     * @return 获取给定元素的值
     */
    private static Object getNodeValue(Element element) {
        NodeList childNodes = element.getChildNodes();
        if (childNodes.getLength() == 1 && childNodes.item(0) instanceof Text) {
            return childNodes.item(0).getTextContent().trim();
        } else {
            Map<String, Object> childMap = new LinkedHashMap<>();
            convertElementToYaml(element, childMap);
            return childMap;
        }
    }
}
